<?php

namespace System\library\config;

use System\library\Session;
use System\library\database\sql\SqlServer;

/**
 * <b>Class</b> :        SqlConfig<br>
 * <b>Fonction</b> :     Permet la gestion de configuration sql
 * <br>
 * @version              1.0
 * @author               Steuf
 * @license              Licence publique générale GNU http://www.gnu.org/licenses/gpl.html
 * @copyright            Copyright 2011 - MidichloriansPHP and contributors
 * @package              System
 * @subpackage           library\config
 */
class SqlConfig {
    const MASTER_SERVER = 1;
    const SLAVE_SERVER = 2;
    /**
     * Charset
     *
     * @access private
     * @static
     * @var string
     */
    private static $charset = 'UTF8';
    /**
     * Instance container of SqlConfig class
     *
     * @access private
     * @static
     * @var array
     */
    private static $instance = array();
    /**
     * List of masters servers
     *
     * @access private
     * @var array
     */
    private static $masters = array();
    /**
     * List of slaves servers
     *
     * @access private
     * @var array
     */
    private static $slaves = array();
    /**
     * Set if balancing is persistent for a user session
     *
     * @access private
     * @var bool
     */
    private $persistentBalancing = false;
    /**
     * Name of this configuration
     *
     * @access private
     * @var string
     */
    protected $cfgname = null;

    /**
     * Constructor of SqlConfig
     *
     * @access private
     * @param string $cfgname Sql config name
     * @return void
     */
    private function __construct($cfgname) {
        // Set the database name for this configuration
        $this->cfgname = $cfgname;
    }

    /**
     * Check if it have configuration for this config name
     *
     * @access public
     * @static
     * @param string $cfgname Sql config name
     * @return bool
     */
    public static function haveConfig($cfgname) {
        if (!is_string($cfgname))
            throw new \Exception('Database name must de a string');
        return isset(self::$instance[md5($cfgname)]);
    }

    /**
     * Get the instance of SqlConfig
     *
     * @access public
     * @static
     * @param string $cfgname Sql config name
     * @return array SqlConfig
     */
    public static function getInstance($cfgname) {
        if (!is_string($cfgname))
            throw new \Exception('Config name must de a string');
        $dbHash = md5($cfgname);
        if (!isset(self::$instance[$dbHash]) || !is_object(self::$instance[$dbHash]) || !self::$instance[$dbHash] instanceof SqlConfig)
            self::$instance[$dbHash] = new SqlConfig($cfgname);
        return self::$instance[$dbHash];
    }

    /**
     * Set if master and slave server selected must be the same for current client session
     *
     * @access public
     * @param bool $boolean
     * @return object SqlConfig
     */
    public function setPersistentBalancing($boolean) {
        if (!is_bool($boolean))
            throw new \Exception('The parameter must be a boolean');
        $this->persistentBalancing = $boolean;
        return $this;
    }

    /**
     * Get a master server for write
     *
     * @access public
     * @param void
     * @return array SqlServer
     */
    public function getMasterServer() {
        $nMasters = self::countMasters();
        // It must be at least have 1 master server
        if ($nMasters == 0)
            throw new \Exception('You must add at least one master server');
        // If it have only 1 master return it
        if ($nMasters == 1)
            return self::$masters[0];

        // Enter on load balancing for masters
        if ($this->persistentBalancing) {
            $sessMast = Session::getInstance()->getVariable('SqlConfigMaster');
            // Master server is setted on sessions return this
            if ($sessMast !== null && $sessMast instanceof SqlServer)
                return $sessMast;
            // Select and save master server
            $master = self::$masters[array_rand(self::$masters)];
            Session::getInstance()->addVariable('SqlConfigMaster', $master, true);
            return $master;
        }else {
            return self::$masters[array_rand(self::$masters)];
        }
    }

    /**
     * Get a slave server for read
     *
     * @access public
     * @param void
     * @return array SqlServer
     */
    public function getSlaveServer() {
        $nSlaves = self::countSlaves();
        // If it don't have slave server, return a master server
        if ($nSlaves == 0)
            return $this->getMasterServer();
        if ($nSlaves == 1)
            return self::$slaves[0];
        // Enter on load balancing for slaves
        if ($this->persistentBalancing) {
            $sessSla = Session::getInstance()->getVariable('SqlConfigSlave');
            // Master server is setted on sessions return this
            if ($sessSla !== null && $sessSla instanceof SqlServer)
                return $sessSla;
            // Select and save master server
            $slave = self::$slaves[array_rand(self::$slaves)];
            Session::getInstance()->addVariable('SqlConfigSlave', $slave, true);
            return $slave;
        }else {
            return self::$slaves[array_rand(self::$slaves)];
        }
    }

    /**
     * Add a master server
     *
     * @access public
     * @param SqlServer $server
     * @return object SqlConfig
     */
    public function addMaster(SqlServer $server) {
        if ($this->existServer($server, self::MASTER_SERVER))
            throw new \Exception('This master server is already registered on this configuration');
        self::$masters[] = $server;
        return $this;
    }

    /**
     * Add slave server
     *
     * @access public
     * @param SqlServer $server
     * @return object SqlConfig
     */
    public function addSlave(SqlServer $server) {
        if ($this->existServer($server, self::SLAVE_SERVER))
            throw new \Exception('This slave server is already registered on this configuration');
        self::$slaves[] = $server;
        return $this;
    }

    /**
     * Checking if server is already registered
     *
     * @param SqlServer $server
     * @param const $type Constant of SqlConfig
     * @return bool
     */
    protected function existServer(SqlServer $server, $type) {
        switch ($type) {
            case self::MASTER_SERVER:
                foreach (self::$masters as &$master)
                    if ($master == $server)
                        return true;
                break;
            case self::SLAVE_SERVER:
                foreach (self::$slaves as &$slave)
                    if ($slave == $server)
                        return true;
                break;
            default:
                throw new \Exception('Server type ' . $type . ' don\'t exist !');
        }
        return false;
    }

    /**
     * Load configuration from ini file
     *
     * @access public
     * @static
     * @param string $configName name of config
     * @param string $file ini file
     * @retur void
     */
    public static function loadIniFile($configName, $file) {
        if (!\file_exists($file))
            throw new \Exception('File "' . $file . "' don\'t exists");
        $datas = parse_ini_file($file, true);
        if (!$datas)
            throw new \Exception('File "' . $file . "' is not a valid ini file");
        self::load($configName, $datas);
    }

    /**
     * Load configuration from a ini string
     *
     * @access public
     * @static
     * @param string $configName name of config
     * @param String $string ini string
     * @return void
     */
    public static function loadIniString($configName, $string) {
        $datas = parse_ini_string($string, true);
        if (!$datas)
            throw new \Exception('Parameter is not a valid ini string');
        self::load($configName, $datas);
    }

    /**
     * Load configuration from an array
     * @access private
     * @static
     * @param string $configName name of config
     * @param array $array
     * @return void
     */
    private static function load($configName, $array) {
        // Load constants
        foreach ($array as $type => &$config) {
            if ($type == 'charset') {
                self::setCharset($config);
            } else {
                if (is_array($config) && count($config) > 0) {
                    $server = new SQLServer();
                    foreach ($config as $attr => &$value) {
                        $setter = 'set' . \ucfirst(str_replace('_', '', preg_replace('#_([a-z]{1})#e', "strtoupper('$1')", $attr)));
                        if (!\method_exists($server, $setter)) {
                            $refl = new \ReflectionClass('SqlServer');
                            $props = $refl->getProperties();
                            $propLst = '';
                            foreach ($props as &$prop)
                                $propLst .= $prop->getName() . ', ';
                            throw new \Exception('Sql server have no attribute "' . $attr . '", available attributes are: ' . rtrim($propLst, ', '));
                        }
                        $server->{$setter}($value);
                    }
                    if (\stripos($type, 'slave') === 0)
                        self::getInstance($configName)->addSlave($server);
                    elseif (\stripos($type, 'master') === 0)
                        self::getInstance($configName)->addMaster($server);
                }
            }
        }
    }

    /**
     * Set charset of sql connexion
     *
     * @access public
     * @static
     * @param string $charset
     * @return void
     */
    public static function setCharset($charset) {
        if (!is_string($charset))
            throw new \Exception('Charset parameter must be a string');
        self::$charset = $charset;
    }

    /**
     * Get current charset of sql configuration
     *
     * @access public
     * @static
     * @param void
     * @return string charset
     */
    public static function getCharset() {
        return self::$charset;
    }

    public static function countConfig(){
        return count(self::$instance);
    }
    public static function countMasters(){
        return count(self::$masters);
    }
    public static function countSlaves(){
        return count(self::$slaves);
    }

}

?>

